package ifgi.potwc.ipt.sensor;

import ifgi.potwc.ipt.R;
import ifgi.potwc.ipt.database.IptDatabase;
import ifgi.potwc.ipt.database.IptDatabaseManager;
import ifgi.potwc.ipt.store.Store;
import ifgi.potwc.ipt.ui.MainActivity;
import ifgi.potwc.ipt.ui.SettingsActivity;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

/**
 * This class handles the phone sensors and controls the IOIOService.
 * 
 * @author saschak.
 */
public class IptSensorService extends Service implements SensorEventListener
{
    // General attributes
    private Context ctx;
    private SharedPreferences prefs;
    private Editor editor;

    // Database Manager
    private IptDatabaseManager iptDbm;

    // Timer and TimerTask
    private Timer timer;
    private LocationTimerTask locationTimerTask;

    // Sensor related attributes
    private LocationManager locationManager;
    private IptLocationListener iptLocationListener;
    private Sensor accelerometer;
    private SensorManager sm;

    // Needed for first accelerometer measurement
    private boolean initialized = false;

    // Last measured accelerometer values
    private float lastX;
    private float lastY;
    private float lastZ;

    // Earth gravity constant
    private final float g = SensorManager.GRAVITY_EARTH;

    // Treshold for crash detection
    private int threshold;

    // Used for increasing the service's priority
    private static final int NOTIFICATION_ID = 123;

    @SuppressWarnings("deprecation")
    @Override
    public void onCreate()
    {
	super.onCreate();

	// Initialize attributes
	ctx = getApplicationContext();
	prefs = SettingsActivity.getPrefs((ContextWrapper) ctx);
	editor = prefs.edit();
	threshold = prefs.getInt("pref_crash_threshold", 10);
	iptDbm = new IptDatabaseManager(ctx);
	timer = new Timer();
	locationTimerTask = new LocationTimerTask();
	sm = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
	accelerometer = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
	locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
	iptLocationListener = new IptLocationListener();

	// Increasing the service's priority and notify the user
	final Notification notification = new Notification(R.drawable.ic_ipt,
		"Parcel tracking has been started", System.currentTimeMillis());
	final Intent intent = new Intent(ctx, MainActivity.class);
	final PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0,
		intent, 0);
	notification.setLatestEventInfo(this, "Sensor Service",
		"Sensors have been activated", pendingIntent);
	startForeground(NOTIFICATION_ID, notification);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
	Log.d("IptSensorService", "started");

	// Get the sensor reading frequency
	long sensorReadingFreq = Long.parseLong(prefs.getString(
		"pref_sensors_freq", "15")) * Store.MINUTES;

	Log.d("sensorReadingFreq", "" + sensorReadingFreq);

	// Start the timer for the timestamp
	timer.scheduleAtFixedRate(locationTimerTask, 0, sensorReadingFreq);

	// Register listener for the accelerometer
	sm.registerListener(this, accelerometer,
		SensorManager.SENSOR_DELAY_NORMAL);

	// Start measuring values from the IOIO
	startService(new Intent(ctx, IptIOIOService.class));

	// If the system kills the service, it will be recreated and called
	// with the last intent that was delivered to it.
	return Service.START_STICKY;
    }

    @Override
    public void onDestroy()
    {
	// Stop the timer
	timer.cancel();
	
	// Stop measuring values from the IOIO
	stopService(new Intent(ctx, IptIOIOService.class));
	
	// Unregister listener for the accelerometer
	sm.unregisterListener(this);

	// Log.d("sensorService", "SensorService stopped");

	// Inform the user that the Sensor service has been stopped
	Toast.makeText(ctx, R.string.sensorServiceStopped, Toast.LENGTH_SHORT)
		.show();

	super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent)
    {
	// Binding is not used, so return null
	return null;
    }

    // ***************************************************************************
    // Accelerometer's SensorEventListener methods

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy)
    {
	// Do something here if sensor accuracy changes
    }

    @Override
    public void onSensorChanged(SensorEvent event)
    {
	if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
	{
	    getAccelerometer(event);
	}
    }

    /**
     * Measures values of the device's accelerometer. In case of a "crash" a
     * force value gets saved to the application's database.
     * 
     * @param event
     */
    public void getAccelerometer(SensorEvent event)
    {
	// Measured values for the three coordinate axes
	float x = event.values[0];
	float y = event.values[1];
	float z = event.values[2];

	// Initialize lastX, lastY & lastZ at the first measurement
	if (!initialized)
	{
	    lastX = x;
	    lastY = y;
	    lastZ = z;

	    initialized = true;

	    return;
	}

	// Current acceleration square root
	double curAccSqrt = Math.abs((Math.sqrt(x * x + y * y + z * z) - g));

	// Last acceleration square root
	double lastAccSqrt = Math.abs((Math.sqrt(lastX * lastX + lastY * lastY
		+ lastZ * lastZ) - g));

	// Difference between last and current acceleration square root
	double deltaSqrts = lastAccSqrt - curAccSqrt;

	// Save value in database
	if (deltaSqrts >= threshold)
	{
	    // Current feed ID from SharedPreferences
	    String feedID = prefs.getString(Store.FEED_ID, "");

	    // Current timestamp
	    String timestamp = Store.getTimestamp();

	    // Round value to 2 decimals
	    deltaSqrts = Store.round(deltaSqrts, 2);

	    // Save value in the database
	    iptDbm.saveValuesInDatabase(feedID, timestamp, deltaSqrts,
		    IptDatabase.ACC);
	}

	// Set lastX, lastY, and lastZ for the next event
	lastX = x;
	lastY = y;
	lastZ = z;
    }

    // ***************************************************************************
    // Location listener

    private class IptLocationListener implements LocationListener
    {
	@Override
	public void onLocationChanged(Location location)
	{
	    Log.d("onLocationChanged", "called");

	    if (location != null)
	    {
		// Current position
		double lat = location.getLatitude();
		double lon = location.getLongitude();

		// Round latitude and longitude to precision of 0.1m
		lat = Store.round(lat, 6);
		lon = Store.round(lon, 6);

		// Current Feed ID
		String feedID = prefs.getString(Store.FEED_ID, "");

		// "Current" timestamp
		String timestamp = prefs.getString("timestamp",
			Store.getTimestamp());

		// Save values in the database
		iptDbm.saveValuesInDatabase(feedID, timestamp, lat,
			IptDatabase.LAT);
		iptDbm.saveValuesInDatabase(feedID, timestamp, lon,
			IptDatabase.LON);

		// Inform the user about measured coordinates
		showMeasuredDataAsToast(feedID, timestamp, IptDatabase.LAT, lat);
		showMeasuredDataAsToast(feedID, timestamp, IptDatabase.LON, lon);
	    }
	}

	@Override
	public void onProviderDisabled(String provider)
	{
	    Toast.makeText(ctx, provider + " disabled", Toast.LENGTH_LONG)
		    .show();
	}

	@Override
	public void onProviderEnabled(String provider)
	{
	    Toast.makeText(ctx, provider + " enabled", Toast.LENGTH_LONG)
		    .show();
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras)
	{
	    String providerStatus = "";

	    switch (status)
	    {
	    case LocationProvider.AVAILABLE:
		providerStatus = "available";
		break;
	    case LocationProvider.OUT_OF_SERVICE:
		providerStatus = "out of service";
		break;
	    case LocationProvider.TEMPORARILY_UNAVAILABLE:
		providerStatus = "temporarily unavailable";
		break;
	    default:
		break;
	    }

	    Toast.makeText(ctx,
		    provider + " status changed to " + providerStatus,
		    Toast.LENGTH_LONG).show();
	}
    }

    // ***************************************************************************
    // Timer task

    private class LocationTimerTask extends TimerTask
    {
	@Override
	public void run()
	{
	    // Save a timestamp in the SharedPreferences
	    editor.putString("timestamp", Store.getTimestamp());
	    editor.commit();

	    // Get location
	    locationManager.requestSingleUpdate(
		    LocationManager.NETWORK_PROVIDER, iptLocationListener,
		    getMainLooper());
	}
    }

    // ***************************************************************************
    // UI feedback

    /**
     * Shows a toast containing the feed ID, timestamp, datastream, and measured
     * coordinates.
     * 
     * @param _feedID
     * @param _timestamp
     * @param _datastream
     * @param _value
     */
    public void showMeasuredDataAsToast(String _feedID, String _timestamp,
	    String _datastream, double _value)
    {
	String message = "Feed ID: " + _feedID + "\n" + "Timestamp: "
		+ _timestamp + "\n" + _datastream + ": " + _value;

	Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show();
    }
}